﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace UI
{
    public class UIScrollRectMethod
    {
        public enum ScrollRectAutoMoveType
        {
            Vertical,
            Horizontal
        }

        /// <summary>
        /// 根据Content内容的数量和尺寸，自动调整滚动视图的Viewport大小。
        /// 目前只支持垂直滚动视图。
        /// </summary>
        /// <param name="scrollRect">滚动视图</param>
        /// <param name="maxDisplayItems">Viewport能显示的最大Item数量。
        /// 注1：本函数会根据每个Item的实际尺寸来计算Viewport的大小，所以自动调整后的Viewport不会令Item显示发生截断。
        /// 注2：为确保本函数所计算出的尺寸是正确的，请确保所修改的ScrollRect的Content是使用了VerticalLayoutGroup，
        ///      且设置正确，以及垂直方向没有处于ContentSizeFitter的控制下。
        /// </param>
        public static float AutoAdjustScrollViewportSize(ScrollRect scrollRect, int maxDisplayItems)
        {
            // 前置检查：ScrollRect，Viewport，Content缺一不可
            if (scrollRect == null) { return -1; }

            var viewport = scrollRect.viewport;
            if (viewport == null) { return -1; }

            var content = scrollRect.content;
            if (content == null) { return -1; }

            if (maxDisplayItems < 0)
            {
                maxDisplayItems = 0;
            }

            if (viewport != null && content != null)
            {
                float itemSpacing = 0;
                float topPadding = 0;
                float bottomPadding = 0;

                var verticalLayoutGroup = content.GetComponent<VerticalLayoutGroup>();

                if (verticalLayoutGroup != null)
                {
                    itemSpacing = verticalLayoutGroup.spacing;
                    topPadding = verticalLayoutGroup.padding.top;
                    bottomPadding = verticalLayoutGroup.padding.bottom;
                }

                float adjustedViewportHeight = 0.0f;
                {
                    bool itemAdded = false;
                    float lastItemHeight = 0.0f;

                    var itemCount = content.childCount;

                    for (int i = 0; i < itemCount && i < maxDisplayItems; i++)
                    {
                        var itemTran = content.GetChild(i);
                        var item = itemTran.gameObject;

                        if (item != null && item.activeSelf)
                        {
                            RectTransform itemRectTran = item.GetComponent<RectTransform>();

                            if (itemRectTran != null)
                            {
                                itemAdded = true;
                                adjustedViewportHeight += itemRectTran.sizeDelta.y;
                                adjustedViewportHeight += itemSpacing;

                                lastItemHeight = itemRectTran.sizeDelta.y;
                            }
                        }
                    }

                    if (itemAdded && itemCount > maxDisplayItems)
                    {
                        adjustedViewportHeight -= itemSpacing;
                        adjustedViewportHeight -= lastItemHeight * 0.5f;
                    }
                }

                {
                    adjustedViewportHeight += topPadding;
                    adjustedViewportHeight += bottomPadding;
                }

                // 为滚动视图的viewport设置计算后的高度
                {
                    var sizeDelta = viewport.sizeDelta;
                    sizeDelta.y = adjustedViewportHeight;
                    viewport.sizeDelta = sizeDelta;
                }
                return adjustedViewportHeight;
            }
            return -1;
        }

        /// <summary>
        /// 在给定的scrollRect节点下，查找给定的targetItem，并尝试滚动到该targetItem。
        /// 
        /// 注：1.目前仅支持纵向滚动。
        ///     2.scrollRect的content的RectTransform，纵向不能使用拉伸对齐。
        ///     3.本函数会尝试将targetItem滚动到scrollRect的viewport的中心，但不会使content超过scrollRect的最大最小滚动位置。
        /// </summary>
        /// <param name="scrollRect"></param>
        /// <param name="targetItem"></param>
        public static void ScrollToItem(ScrollRect scrollRect, GameObject targetItem, ScrollRectAutoMoveType moveStyle)
        {
            // 前置检查：ScrollRect，Viewport，Content缺一不可
            if (scrollRect == null) { return; }

            var viewport = scrollRect.viewport;
            if (viewport == null) { return; }

            var content = scrollRect.content;
            if (content == null) { return; }

            // 要滚动到的item也不能少
            if (targetItem == null) { return; }

            // 并且要求该item的父节点必须是content节点
            if (targetItem.transform.parent != content) { return; }

            // 确认了所有参数后，就可以进行滚动了
            ScrollToItemInternal(scrollRect, targetItem, moveStyle);
        }

        public static void ScrollToItem(ScrollRect scrollRect, Func<GameObject, bool> findItemFunc, ScrollRectAutoMoveType moveStyle)
        {
            // 前置检查：ScrollRect，Viewport，Content缺一不可
            if (scrollRect == null) { return; }

            var viewport = scrollRect.viewport;
            if (viewport == null) { return; }

            var content = scrollRect.content;
            if (content == null) { return; }

            // 查找Item的回调当然也不能少
            if (findItemFunc == null) { return; }

            // 滚动逻辑
            {
                GameObject item = null;

                // 通过传入的回调函数查找Item，并获取Item的尺寸和位置
                {
                    var contentTran = content.transform;
                    var contentTranChildCount = contentTran.childCount;

                    for (int i = 0; i < contentTranChildCount; i++)
                    {
                        var tmpItem = contentTran.GetChild(i);

                        if (findItemFunc(tmpItem.gameObject))
                        {
                            item = tmpItem.gameObject;
                            break;
                        }
                    }
                }

                // 如果找到了Item，就进行滚动
                if (item != null)
                {
                    ScrollToItemInternal(scrollRect, item, moveStyle);
                }
            }
        }

        private static void ScrollToItemInternal(ScrollRect scrollRect, GameObject item, ScrollRectAutoMoveType moveStyle)
        {
            if (item == null || scrollRect == null) { return; }

            var itemRectTran = item.GetComponent<RectTransform>();
            if (itemRectTran == null) { return; }

            var contentTran = scrollRect.content.GetComponent<RectTransform>();
            if (contentTran == null) { return; }

            var viewportTran = scrollRect.viewport.GetComponent<RectTransform>();
            if (viewportTran == null) { return; }

            float normPos = 0;

            // 计算出normalizedPosition后，就可以开始滚动了
            // 注：滚动到底部为0.0f, 顶部为1.0f
            switch (moveStyle)
            {
                case ScrollRectAutoMoveType.Vertical:
                    {
                        if (CalculateVerticalScrollRectNormalizedMoveValue(ref normPos,
                            scrollRect, contentTran, viewportTran, itemRectTran))
                        {
                            ScrollToVerticalNormalizedPostion(scrollRect, normPos);
                        }
                    }
                    break;
                case ScrollRectAutoMoveType.Horizontal:
                    {
                        if (CalculateHorizontalScrollRectNormalizedMoveValue(ref normPos,
                           scrollRect, contentTran, viewportTran, itemRectTran))
                        {
                            ScrollToHorizontalNormalizedPostion(scrollRect, normPos);
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        private static bool CalculateVerticalScrollRectNormalizedMoveValue(ref float normPos,
            ScrollRect scrollRect,RectTransform contentTran,RectTransform viewportTran, RectTransform itemRectTran)
        {
            var contentHeight = contentTran.rect.height;
            var viewportHeight = viewportTran.rect.height;

            // 如果content尺寸小于视窗的尺寸，则无法进行任何滚动，直接返回。
            if (contentHeight < viewportHeight)
            {
                return false;
            }

            // NOTE: ScrollView坐标系中，content顶部坐标为（0.0f, 0.0f），底部坐标为（0.0f, -contentHeight）
            // NOTE: 能够滚动到ScrollView顶部的坐标：最大值（0.0f, 0.0f），最小值（0.0f, -(contentHeight - viewportHeight)）
            // NOTE: 可滚动距离 = 滚动最大值 - 滚动最小值
            var minScrollVal = -(contentHeight - viewportHeight);
            var maxScrollVal = 0.0f;
            var scrollHeight = maxScrollVal - minScrollVal;

            var itemPosY = itemRectTran.anchoredPosition.y;
            var itemHeight = itemRectTran.rect.height;
            var itemPivotY = itemRectTran.pivot.y;
            var itemScrollValTop = itemPosY + itemPivotY * itemHeight;
            var itemScrollValBottom = itemPosY - (1.0f - itemPivotY) * itemHeight;

            var currNormPos = scrollRect.verticalNormalizedPosition;
            var currScrollVal = -((1.0f - currNormPos) * scrollHeight);

            float finalScrollVal;

            // 计算目标滚动值时，分两种情况：
            // 1.目标GameObject的尺寸大于等于视窗尺寸：总是尝试将GameObject滚动到与视窗的顶部对齐。
            // 2.目标GameObject的尺寸小于视窗尺寸：总是尝试将GameObject滚动到刚好能够完整显示在视窗中。
            // 计算滚动坐标值时需要考虑滚动范围的限制。
            if (itemHeight >= viewportHeight)
            {
                finalScrollVal = itemScrollValTop;
            }
            else
            {
                // 先看一下目标GameObject是不是已经完整显示在视窗中了。
                // 如果是的话，则无需进行滚动。
                // 注：视窗顶部的滚动坐标一定在滚动范围内，但视窗底部的不一定。
                var viewportScrollValTop = currScrollVal;
                var viewportScrollValBottom = currScrollVal - viewportHeight;

                if (itemScrollValTop <= viewportScrollValTop && itemScrollValBottom >= viewportScrollValBottom)
                {
                    return false;
                }
                else
                {
                    // 先确定为了让目标GameObject滚动至于视窗的顶部对齐，是需要将ScrollView向哪个方向滚动。
                    var diff = itemScrollValTop - currScrollVal;

                    if (diff <= 0.0f) // 向上滚动
                    {
                        finalScrollVal = itemScrollValBottom + viewportHeight;
                    }
                    else // 向下滚动
                    {
                        finalScrollVal = itemScrollValTop;
                    }
                }
            }

            if (Mathf.Abs(finalScrollVal - currScrollVal) < 0.001f) // 如当前滚动位置几乎就在目标位置，无需滚动
            {
                return false;
            }

            normPos = 1.0f - Mathf.Clamp01(Mathf.Abs(finalScrollVal / scrollHeight));
            return true;
        }
        
        /// <summary>
        /// 计算指定item,并让其滑动到最左边
        /// </summary>
        private static bool CalculateHorizontalScrollRectNormalizedMoveValue(ref float normPos,
            ScrollRect scrollRect, RectTransform contentTran, RectTransform viewportTran, RectTransform itemRectTran)
        {
            var contentWidth = contentTran.rect.width;//实际尺寸
            var viewportWidth = viewportTran.rect.width; //viewportWidth = content的实际长度 - content坐标尺寸 (contentWidht 比viewport 多出来的长度就是可移动的长度)

            // 如果content实际尺寸小于 视窗的尺寸，则无法进行任何滚动，直接返回。
            if (contentWidth < viewportWidth)
            {
                return false;
            }

            // NOTE: ScrollView坐标系中，content最左边边坐标为（0.0f, 0.0f），最右边坐标为（-contentWidth, 0.0f）
            // NOTE: 能够滚动到ScrollView左边的坐标：最大值（0.0f, 0.0f），最小值（-(contentWidth - viewportWidth)，0.0f）
            // NOTE: 可滚动距离 = 滚动最大值 - 滚动最小值
            var minScrollVal = -(contentWidth - viewportWidth);
            var maxScrollVal = 0.0f;
            var scrollWidth = maxScrollVal - minScrollVal;//scrollRect 能滑动的最大长度了 (contentRealLen - viewportLen)

            var itemPosX = itemRectTran.anchoredPosition.x;
            var itemWidth = itemRectTran.rect.width;
            var itemPivotX = itemRectTran.pivot.x;
            var itemScrollValLeft = itemPosX - itemPivotX * itemWidth;
            var itemScrollValRight = itemPosX + (1.0f - itemPivotX) * itemWidth;

            var currNormPos = scrollRect.horizontalNormalizedPosition;
            var currScrollVal = -(currNormPos * scrollWidth);//当前的进度值 所占的能滑动的content坐标长度

            float finalScrollVal;

            // 计算目标滚动值时，分两种情况：
            // 1.目标GameObject的尺寸大于等于视窗尺寸：总是尝试将GameObject滚动到与视窗的左边对齐。
            // 2.目标GameObject的尺寸小于视窗尺寸：总是尝试将GameObject滚动到刚好能够完整显示在视窗中,。
            // 计算滚动坐标值时需要考虑滚动范围的限制。
            var hGroup = scrollRect.content.GetComponent<HorizontalLayoutGroup>();
            float space = 0;
            if (hGroup != null)
            {
                space = hGroup.spacing;
                //0
            }

            if (itemWidth >= viewportWidth) //将GameObject 移到最左边
            {
               
                finalScrollVal = itemScrollValLeft - space; //finalScrollVal = 已经超出的 + viewPort内的 = itemScrollValLeft , 就是到最左边的位置
            }
            else //看向左还是向右移动
            {
                // 先看一下目标GameObject是不是已经完整显示在视窗中了。
                // 如果是的话，则无需进行滚动。
                // 注：视窗顶部的滚动坐标一定在滚动范围内，但视窗底部的不一定。
                var viewportScrollValLeft = currScrollVal;
                var viewportScrollValRight = currScrollVal + viewportWidth;

                if (itemScrollValLeft >= viewportScrollValLeft && itemScrollValRight <= viewportScrollValRight)
                {
                    return false;
                }
                else
                {
                    // 先确定为了让目标GameObject滚动至于视窗的顶部对齐，是需要将ScrollView向哪个方向滚动。
                    var diff = itemScrollValLeft - currScrollVal;

                    if (diff <= 0.0f) // 向右滚动
                    {
                        finalScrollVal = itemScrollValRight + viewportWidth;
                    }
                    else // 向左滚动
                    {
                        finalScrollVal = itemScrollValLeft - space;
                    }
                }
            }

            if (Mathf.Abs(finalScrollVal - currScrollVal) < 0.001f) // 如当前滚动位置几乎就在目标位置，无需滚动
            {
                return false;
            }

            normPos = Mathf.Clamp01(Mathf.Abs(finalScrollVal / scrollWidth));
            return true;
        }

        private static List<UGUITweenValue> __scrollToNormalizedPosition_tweenerListReuse = new List<UGUITweenValue>();

        public static void ScrollToVerticalNormalizedPostion(ScrollRect scrollRect, float targetNormPos)
        {
            ScrollToNormalizedPosition(scrollRect, targetNormPos, scrollRect.verticalNormalizedPosition, OnUpdateVerticalTweenScrollViewValue);
        }

        public static void ScrollToHorizontalNormalizedPostion(ScrollRect scrollRect, float targetNormPos)
        {
            ScrollToNormalizedPosition(scrollRect, targetNormPos, scrollRect.horizontalNormalizedPosition, OnUpdateHorizontalTweenScrollViewValue);
        }

        /// <summary>
        /// 滚动到
        /// </summary>
        /// <param name="scrollRect"></param>
        /// <param name="normPos"></param>
        public static void ScrollToNormalizedPosition(ScrollRect scrollRect, float targetNormPos,float currNormPos, Action<UGUITweenValue, float> onUpdateCall, float duration = 0.3f, int tweenerGroup = 999)
        {
            if (scrollRect == null)
            {
                return;
            }

            var destNormPos = targetNormPos;

            UGUITweenValue tweener = null;

            {
                __scrollToNormalizedPosition_tweenerListReuse.Clear();
                scrollRect.GetComponents<UGUITweenValue>(__scrollToNormalizedPosition_tweenerListReuse);

                for (int i = 0; i < __scrollToNormalizedPosition_tweenerListReuse.Count; i++)
                {
                    var tmpTweener = __scrollToNormalizedPosition_tweenerListReuse[i];

                    if (tmpTweener.tweenGroup == tweenerGroup)
                    {
                        tweener = tmpTweener;
                        break;
                    }
                }
                __scrollToNormalizedPosition_tweenerListReuse.Clear();
            }

            if (tweener == null)
            {
                tweener = scrollRect.gameObject.AddComponent<UGUITweenValue>();
                tweener.onUpdate += onUpdateCall;
                tweener.tweenGroup = tweenerGroup;
            }

            tweener.from = currNormPos;
            tweener.to = destNormPos;
            tweener.duration = duration;
            tweener.args = scrollRect;
            tweener.method = UGUITweener.Method.EaseOut;
            tweener.ResetToBeginning();
            tweener.PlayForward();
        }

        private static void OnUpdateVerticalTweenScrollViewValue(UGUITweenValue sender, float v)
        {
            var scrollRect = (ScrollRect)sender.args;
            scrollRect.verticalNormalizedPosition = v;
        }
        private static void OnUpdateHorizontalTweenScrollViewValue(UGUITweenValue sender,float h)
        {
            var scrollRect = (ScrollRect)sender.args;
            scrollRect.horizontalNormalizedPosition = h;
        }

        public static void UpdateScrollerArrows(ScrollRect scroll, GameObject forwrdArrows, GameObject backArrows, bool verticalorhoriz)
        {
            float valueMin = 0.01f;
            float valueMax = 0.99f;
            float value = verticalorhoriz ? scroll.verticalNormalizedPosition : scroll.horizontalNormalizedPosition;
            if (value <= valueMin)
            {
                if (forwrdArrows.activeInHierarchy == true) forwrdArrows.SetActiveEx(false);
                if (backArrows.activeInHierarchy == false) backArrows.SetActiveEx(true);
            }
            else if (value > valueMin && value < valueMax)
            {
                if (forwrdArrows.activeInHierarchy == false) forwrdArrows.SetActiveEx(true);
                if (backArrows.activeInHierarchy == false) backArrows.SetActiveEx(true);
            }
            else
            {
                if (forwrdArrows.activeInHierarchy == false) forwrdArrows.SetActiveEx(true);
                if (backArrows.activeInHierarchy == true) backArrows.SetActiveEx(false);
            }
        }
    }
}
